import child_process from "child_process";
import { appAddSvr, appRemoveSvr, isLocalHost } from "../util/util";
import { Application } from "../Application";
import { CliMsg, CliMsgId, miscCfg, SvrInfo, SvrMsg, SvrMsgId } from "../util/defines";
import { logger } from "../util/logger";
import { TcpServer } from "../tcp/TcpServer";
import { TcpSocket } from "../tcp/TcpSocket";
import { TcpUnregedSocket } from "../tcp/TcpUnregedSocket";

/**
 * 主服务器
 */
export class Master {

    app: Application;
    svr: TcpServer;
    svrs: { [sid: string]: MasterSvrSocket } = {};
    svrCount = 0;
    clis: { [cid: number]: MasterCliSocket } = {};

    constructor(app: Application) {
        this.app = app;
        this.svr = new TcpServer(app.svr.port, this.onStart.bind(this), this.onScoket.bind(this));
    }

    private onStart() {
        logger.info(`${this.app.svr.id}已启动,监听${this.app.svr.port}端口,开始启动服务器群`, this, this.onStart);
        this.runSvrs();
    }

    private onScoket(socket: TcpSocket) {
        new TcpUnregedSocket(this.app, socket, this.onScoketReg.bind(this));
    }

    private onScoketReg(socket: TcpSocket, svr: SvrInfo) {
        if (this.app.isStopping) {
            socket.close();
            return;
        }
        if (svr) {
            new MasterSvrSocket(this.app, socket, svr);
        } else {
            new MasterCliSocket(this.app, socket);
        }
    }

    /**
     * 启动服务器群
     */
    private runSvrs() {
        for (let stype in this.app.svrCfg) {
            let svrs = this.app.svrCfg[stype];
            for (let svr of svrs) {
                this.runSvr(svr);
            }
        }
    }

    /**
     * 启动单个服务器
     * @param svr 
     */
    private runSvr(svr: SvrInfo) {
        if (isLocalHost(svr.host)) {
            //本地启动
            let isTs = this.app.main.endsWith(".ts");
            let cmd = isTs ? "ts-node" : process.execPath;
            let args = [this.app.main, `id=${svr.id}`, `env=${this.app.env}`];
            this.spawnProcess(cmd, args);
        } else {
            //ssh启动
            //...还没实现
        }
    }

    /**
     * 创建子进程
     * @param cmd 
     * @param args 
     */
    private spawnProcess(cmd: string, args: string[]) {
        let child: child_process.ChildProcessWithoutNullStreams;
        if (cmd === "ts-node") {
            child = child_process.exec(cmd + " " + args.join(" "));
        } else {
            child = child_process.spawn(cmd, args)
        }

        child.stderr.on('data', chunk => {
            let msg = chunk.toString();
            process.stderr.write(msg);
        });

        child.stdout.on('data', chunk => {
            process.stdout.write(chunk.toString());
        });

        child.on('exit', code => {
            if (code !== 0) logger.error(`子进程发生错误,code:${code},cmd:${cmd}`, this, this.spawnProcess);
        });
    }

    /**
     * 添加服务器
     * @param svr 
     */
    addSvr(svr: MasterSvrSocket) {
        ++this.svrCount;
        this.svrs[svr.remoteSvr.id] = svr;
        appAddSvr(this.app, svr.remoteSvr);
    }

    /**
     * 移除服务器
     * @param svr 
     */
    removeSvr(svr: MasterSvrSocket) {
        --this.svrCount;
        delete this.svrs[svr.remoteSvr.id];
        appRemoveSvr(this.app, svr.remoteSvr);
        if (this.app.isStopping && this.svrCount === 0) {
            this.onStop();
        }
    }

    /** 获取所有服务器的id */
    getSids() {
        let sids: string[] = [];
        for (let sid in this.app.master.svrs) sids.push(sid);
        return sids;
    }

    /**
     * 添加cli
     * @param cli 
     */
    addCli(cli: MasterCliSocket) {
        this.clis[cli.cid] = cli;
    }

    /**
     * 移除cli
     * @param cli 
     */
    removeCli(cli: MasterCliSocket) {
        delete this.clis[cli.cid];
    }

    /**
     * 停止服务器
     */
    stop(cid?: number, sess?: number) {
        if (this.app.isStopping) return;
        this.app.isStopping = true;
        for (let sid in this.svrs) {
            let svr = this.svrs[sid];
            svr.send(SvrMsgId.stop, { cid, sess });
        }
    }

    private onStop() {
        logger.info("服务器已停止", this, this.onStop);
        process.exit();
    }


    /**
     * 发消息给某个Server
     * @param sid 
     * @param msgId 
     * @param msg 
     * @returns 
     */
    send<MsgId extends SvrMsgId>(sid: string, msgId: MsgId, msg?: SvrMsg<MsgId>) {
        let svr = this.svrs[sid];
        if (!svr) return logger.error(`消息发送失败,服务器${sid}不存在`, this, this.send);
        svr.send(msgId, msg);
    }

    /**
     * 广播消息给所有Server
     * @param msgId 
     * @param msg 
     * @param sids 
     * @param excludeSids 
     */
    broadcast<MsgId extends SvrMsgId>(msgId: MsgId, msg?: SvrMsg<MsgId>, sids?: string[], excludeSids?: { [sid: string]: boolean }) {
        let data = TcpSocket.encode({ msgId, msg });
        if (sids) {
            for (let sid of sids) {
                if (excludeSids && excludeSids[sid]) continue;
                let svr = this.svrs[sid];
                if (!svr) return logger.error(`消息发送失败,服务器${sid}不存在`, this, this.broadcast);
                svr.socket.socket.write(data);
            }
        } else {
            for (let sid in this.svrs) {
                if (excludeSids && excludeSids[sid]) continue;
                this.svrs[sid].socket.socket.write(data);
            }
        }
    }
}

/**
 * 服务器与Master连接的Socket
 */
class MasterSvrSocket {

    app: Application;
    socket: TcpSocket;
    remoteSvr: SvrInfo;
    private _hbTimeoutTimer: NodeJS.Timeout;

    constructor(app: Application, socket: TcpSocket, remoteSvr: SvrInfo) {
        this.app = app;
        this.socket = socket;
        this.remoteSvr = remoteSvr;
        this.socket.on(this.onData.bind(this), this.onClose.bind(this));
        this._hbTimeoutTimer = setTimeout(() => this.onHeartbeatTimeout.bind(this), miscCfg.heartbeatTime + miscCfg.heartbeatTimeout);
        this.send(SvrMsgId.regOk, this.app.master.getSids());
        this.app.master.broadcast(SvrMsgId.svrAdd, remoteSvr.id, null, { [this.remoteSvr.id]: true });
        this.app.master.addSvr(this);
        logger.info(`${this.remoteSvr.id}注册成功`, this, "constructor");
    }

    /**
     * 发送消息与Server进行通信
     * @param msgId 
     * @param msg 
     */
    send<MsgId extends SvrMsgId>(msgId: MsgId, msg?: SvrMsg<MsgId>) {
        this.socket.send({ msgId, msg });
    }

    private onData(data: { msgId: SvrMsgId, msg: SvrMsg }) {
        let { msgId, msg } = data;
        switch (msgId) {
            case SvrMsgId.heartbeat:
                this._hbTimeoutTimer.refresh();
                this.send(SvrMsgId.heartbeat);
                break;

            case SvrMsgId.cliRes:
                let cliRes = msg as SvrMsg<typeof msgId>;
                let cli = this.app.master.clis[cliRes.cid];
                cli && cli.onRes(cliRes.sess, cliRes.sid, cliRes.res);
                break;
        }
    }

    private onClose() {
        clearTimeout(this._hbTimeoutTimer);
        this.app.master.removeSvr(this);
    }

    private onHeartbeatTimeout() {
        logger.error(`${this.remoteSvr.id}心跳包超时`, this, this.onHeartbeatTimeout);
        this.socket.close();
    }
}

/**
 * 命令行与Master连接的Socket
 */
class MasterCliSocket {

    static cid = 0;

    cid: number;
    app: Application;
    socket: TcpSocket;
    private _hbTimeoutTimer: NodeJS.Timeout;
    private _waitResDic: { [sess: number]: { timeoutTimer: NodeJS.Timeout, waits: number, resArr: { sid: string, res: { code: number, msg?: string, data?: any } }[] } } = {};

    constructor(app: Application, socket: TcpSocket) {
        this.app = app;
        this.socket = socket;
        this.cid = ++MasterCliSocket.cid;
        if (MasterCliSocket.cid > 268435455) MasterCliSocket.cid = 1;
        socket.on(this.onData.bind(this), this.onClose.bind(this));
        this._hbTimeoutTimer = setTimeout(() => this.onHeartbeatTimeout.bind(this), miscCfg.heartbeatTime + miscCfg.heartbeatTimeout);
        this.app.master.addCli(this);
        this.send(CliMsgId.regOk);
    }

    /**
     * 发送消息与Cli进行通信
     * @param msgId 
     * @param msg 
     */
    send<MsgId extends CliMsgId>(msgId: MsgId, msg?: CliMsg<MsgId>) {
        this.socket.send({ msgId, msg });
    }

    private onData(data: { msgId: CliMsgId, msg: CliMsg }) {
        try {
            let { msgId, msg } = data;
            switch (msgId) {
                case CliMsgId.heartbeat:
                    this._hbTimeoutTimer.refresh();
                    this.send(CliMsgId.heartbeat);
                    break;

                case CliMsgId.cmd:
                    let cmd = msg as CliMsg<typeof msgId>;
                    if (cmd.all) {
                        this.waitRes(cmd.sess);
                        let res = miscCfg.onCmd(cmd.cmd);
                        res instanceof Promise ? res.then(data => this.onRes(cmd.sess, this.app.svr.id, data)) : this.onRes(cmd.sess, this.app.svr.id, res);
                        this.app.master.broadcast(SvrMsgId.cmd, { cid: this.cid, sess: cmd.sess, cmd: cmd.cmd });
                    } else if (cmd.stypes) {
                        let sids: string[] = [];
                        for (let stype of cmd.stypes) {
                            sids = sids.concat(this.app.getSidsByStype(stype));
                        }
                        for (let i = sids.length; --i >= 0;) {
                            this.waitRes(cmd.sess);
                            let sid = cmd.sids[i];
                            if (!this.app.hasSvr(sid)) {
                                sids.splice(i, 1);
                                this.onRes(cmd.sess, sid, { code: -1, msg: "未找到服务器" });
                            }
                        }
                        this.app.master.broadcast(SvrMsgId.cmd, { cid: this.cid, sess: cmd.sess, cmd: cmd.cmd }, sids);
                    } else if (cmd.sids) {
                        let masterIdx = cmd.sids.indexOf(this.app.svr.id);
                        if (masterIdx > -1) {
                            this.waitRes(cmd.sess);
                            cmd.sids.splice(masterIdx, 1);
                            if (miscCfg.onCmd) {
                                let res = miscCfg.onCmd(cmd.cmd);
                                res instanceof Promise ? res.then(data => this.onRes(cmd.sess, this.app.svr.id, data)) : this.onRes(cmd.sess, this.app.svr.id, res);
                            }
                        }
                        for (let i = cmd.sids.length; --i >= 0;) {
                            this.waitRes(cmd.sess);
                            let sid = cmd.sids[i];
                            if (!this.app.hasSvr(sid)) {
                                cmd.sids.splice(i, 1);
                                this.onRes(cmd.sess, sid, { code: -1, msg: "未找到服务器" });
                            }
                        }
                        this.app.master.broadcast(SvrMsgId.cmd, { cid: this.cid, sess: cmd.sess, cmd: cmd.cmd }, cmd.sids);
                    }
                    break;

                case CliMsgId.stop:
                    let stop = msg as CliMsg<typeof msgId>;
                    this.waitRes(stop.sess);
                    this.app.master.stop(this.cid, stop.sess);
                    break;

                case CliMsgId.reload:
                    let reload = msg as CliMsg<typeof msgId>;
                    let [type, stypeOrPath, handlerName] = reload.reload.split(".");
                    switch (type) {
                        case "msgCfg":
                            this.waitRes(reload.sess, this.app.master.svrCount);
                            this.app.master.broadcast(SvrMsgId.reloadMsgCfg, { cid: this.cid, sess: reload.sess, reload: stypeOrPath });
                            break;
                        case "msg":
                            let sids = stypeOrPath == "*" ? null : this.app.getSidsByStype(stypeOrPath);
                            this.waitRes(reload.sess, sids ? sids.length : this.app.master.svrCount);
                            this.app.master.broadcast(SvrMsgId.reloadMsg, { cid: this.cid, sess: reload.sess, reload: handlerName }, sids);
                            break;
                        case "rpc":
                            if (!stypeOrPath) {
                                this.send(CliMsgId.result, { sess: reload.sess, resArr: [{ sid: this.app.svr.id, res: { code: -1, msg: "重载失败:参数错误" } }] });
                            }
                            this.waitRes(reload.sess, this.app.master.svrCount);
                            this.app.master.broadcast(SvrMsgId.reloadRpc, { cid: this.cid, sess: reload.sess, reload: handlerName ? stypeOrPath + "." + handlerName : stypeOrPath });
                            break;
                        default:
                            this.send(CliMsgId.result, { sess: reload.sess, resArr: [{ sid: this.app.svr.id, res: { code: -1, msg: "重载失败:类型错误" } }] });
                            break;
                    }
                    break;
            }
        } catch (err: any) {
            logger.error(`${this.socket.remoteAddress}数据接收失败` + err.stack, this, this.onData);
            return;
        }
    }

    private waitRes(sess: number, count = 1) {
        let wait = this._waitResDic[sess];
        if (!wait) {
            wait = this._waitResDic[sess] = {
                timeoutTimer: setTimeout(() => {
                    clearTimeout(wait.timeoutTimer);
                    delete this._waitResDic[sess];
                    this.send(CliMsgId.result, { sess, resArr: wait.resArr, timeout: true });
                }, miscCfg.cliTimeout),
                waits: 0,
                resArr: []
            }
        }
        wait.waits += count;
    }

    onRes(sess: number, sid: string, res: { code: number, msg?: string, data?: any }) {
        let wait = this._waitResDic[sess];
        if (wait) {
            wait.resArr.push({ sid, res });
            if (--wait.waits === 0) {
                clearTimeout(wait.timeoutTimer);
                delete this._waitResDic[sess];
                this.send(CliMsgId.result, { sess, resArr: wait.resArr });
            }
        }
    }

    private onClose() {
        clearTimeout(this._hbTimeoutTimer);
        for (let sess in this._waitResDic) {
            clearTimeout(this._waitResDic[sess].timeoutTimer);
            delete this._waitResDic[sess];
        }
        this.app.master.removeCli(this);
    }

    private onHeartbeatTimeout() {
        logger.error(`${this.socket.remoteAddress}心跳包超时`, this, this.onHeartbeatTimeout);
        this.socket.close();
    }
}